package com.atguigu.gmall.item.service.impl;


import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.feign.product.SkuInfoFeignClient;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.vo.CategoryPath;
import com.atguigu.gmall.product.vo.ItemDataVo;
import com.atguigu.starter.cache.annotation.MallCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author lfy
 * @Description
 * @create 2023-07-15 14:06
 */
@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    StringRedisTemplate redisTemplate;

    //锁池：每一个商品id都有自己的锁。
    //1、本地锁，在分布式情况下，无锁锁住集群中的所有机器
    //2、锁池会产生OOM问题
    //分布式锁
    Map<Long, ReentrantLock> lockPool = new ConcurrentHashMap<>();

    @Autowired
    ThreadPoolExecutor executor;


    /**
     * 1、缓存用的Key，不能写死
     * 2、缓存中查出的数据类型，不能写死
     * 3、bitmap用不用，用哪个，怎么用？不能写死
     * 4、用哪个锁，怎么用不能写死
     * 5、得不到锁的业务，睡眠时间要根据业务特性自己定
     * 6、缓存数据的时长不能写死
     *
     * @param skuId sku:info:49-50
     * @return
     */
    @MallCache(
            key = RedisConst.SKUINFO + "#{#params[0]}",
            bitmapName = RedisConst.BITMAP_SKUID,
            bitOffset = "#{#params[0]}",
            lockKey = RedisConst.LOCK_SKUINFO + "#{#params[0]}",
            ttl = 7 * 24 * 3600L
    )
    @Override
    public ItemDataVo getSkuDetail(Long skuId) {
        return getDetail(skuId);
    }

    public ItemDataVo getSkuDetailWithRedisCache(Long skuId) {
        //1、先查缓存
        String json = redisTemplate.opsForValue().get(RedisConst.SKUINFO + skuId);
        //2、判断
        if (!StringUtils.isEmpty(json)) {
            log.info("缓存命中；【{}】商品详情", skuId);
            //3、缓存中有直接返回
            ItemDataVo dataVo = JSONs.jsonStr2Obj(json, ItemDataVo.class);
            return dataVo;
        }

        //4、缓存未命中
        log.info("缓存未命中；【{}】商品详情准备回源", skuId);
        //5、判断位图【防止缓存穿透（随机值穿透攻击）】
        Boolean bit = redisTemplate.opsForValue().getBit(RedisConst.BITMAP_SKUID, skuId);
        if (!bit) {
            //6、位图没有
            log.info("位图没有【{}】；疑似攻击请求，已被拦截", skuId);
            return null;
        }

        //7、位图有；加锁；再回源【防止缓存击穿】，字符串一样就是同一把锁
//        String lock = new String("lock:"+skuId);  //lock:50
//        synchronized (lock.intern()){
//            //加到
//        }

        //原子性初始化锁
        lockPool.putIfAbsent(skuId, new ReentrantLock()); //原子性，如果没有才会放，有就不放
        //直接用
        ReentrantLock lock = lockPool.get(skuId);

        //8、加锁
        boolean tryLock = lock.tryLock();
        if (tryLock) {
            log.info("加锁成功，商品id：【{}】，正在回源", skuId);
            //9、加锁成功；回源
            //回源方法
            ItemDataVo result = getDetail(skuId);
            //10、保存到缓存中
            String jsonStr = JSONs.obj2JsonStr(result);
            redisTemplate.opsForValue().set(RedisConst.SKUINFO + skuId, jsonStr, 7, TimeUnit.DAYS);
            log.info("商品：【{}】回源完成，已缓存", skuId);
            lock.unlock();
            return result;
        }

        //11、没拿到锁，等待一阵直接查缓存即可
        ItemDataVo result = null;
        try {
            log.info("加锁失败，商品id：【{}】，等待一段时间，再查缓存", skuId);
            TimeUnit.MILLISECONDS.sleep(300);
            //12、查询缓存
            String data = redisTemplate.opsForValue().get(RedisConst.SKUINFO + skuId);
            result = JSONs.jsonStr2Obj(data, ItemDataVo.class);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        return result;
    }


    /**
     * 远程查询商品的所有详情数据
     *
     * @param skuId
     * @return
     */
    private ItemDataVo getDetail(Long skuId) {
        ItemDataVo result = new ItemDataVo();


        //1、查询sku的基本信息
        CompletableFuture<Result<SkuInfo>> skuInfoFuture =
                CompletableFuture.supplyAsync(() -> {
                    Result<SkuInfo> skuInfo = skuInfoFeignClient.getSkuInfo(skuId);
                    //准备数据；属性对拷的null值问题
//                    BeanUtils.copyProperties(skuInfo.getData(),result.getSkuInfo());
                    ItemDataVo.SkuInfoVo infoVo = result.getSkuInfo();
                    SkuInfo data = skuInfo.getData();
                    infoVo.setId(data.getId());
                    infoVo.setSpuId(data.getSpuId());
                    infoVo.setSkuDefaultImg(data.getSkuDefaultImg());
                    infoVo.setSkuName(data.getSkuName());
                    infoVo.setWeight(data.getWeight());
                    return skuInfo;
                }, executor);

        //2、查询sku图片信息
        CompletableFuture<Void> imageFuture =
                CompletableFuture.runAsync(() -> {
                    log.info("图片查询线程："+Thread.currentThread().getName());
                    List<ItemDataVo.SkuImageVo> imageVos = skuInfoFeignClient
                            .getSkuImages(skuId)
                            .getData()
                            .stream()
                            .map(item -> {
                                ItemDataVo.SkuImageVo imageVo = new ItemDataVo.SkuImageVo();
                                imageVo.setImgUrl(item.getImgUrl());
                                return imageVo;
                            }).collect(Collectors.toList());
                    result.getSkuInfo().setSkuImageList(imageVos);
                }, executor);

        //3、查询价格
        CompletableFuture<Void> priceFuture =
                CompletableFuture.runAsync(() -> {
                    Result<BigDecimal> skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
                    result.setPrice(skuPrice.getData());
                }, executor);

        //商品服务为了适配其他系统，不能直接返回item需要的vo，每个微服务要自己用什么数据自己封装
        //远程调用、高并发 禁用3张以上表的联表查询


        //4、查询分类层级
        CompletableFuture<Void> categoryFuture =
                skuInfoFuture.thenAcceptAsync(skuInfoResult -> {
                    Long category3Id = skuInfoResult.getData().getCategory3Id();
                    Result<CategoryPath> categoryPath =
                            skuInfoFeignClient.getCategoryPath(category3Id);
                    ItemDataVo.CategoryViewVo viewVo = new ItemDataVo.CategoryViewVo();
                    BeanUtils.copyProperties(categoryPath.getData(), viewVo);
                    result.setCategoryView(viewVo);
                }, executor);


        //5、查询出当前sku对应的spu当时定义的所有销售属性名和值， 并标注那一组名和值是当前sku的销售属性组合
        CompletableFuture<Void> saleAttrFuture =
                skuInfoFuture.thenAcceptAsync(skuInfoResult -> {
                    Long spuId = skuInfoResult.getData().getSpuId();
                    Result<List<SpuSaleAttr>> saleAttrs = skuInfoFeignClient
                            .getSpuSaleAttrAndValueAnnotationSku(skuId, spuId);
                    result.setSpuSaleAttrList(saleAttrs.getData());
                }, executor);


        //6、查询某个spu下的所有sku所有的销售属性组合可能性
        CompletableFuture<Void> valueJsonFuture =
                skuInfoFuture.thenAcceptAsync(skuInfoResult -> {
                    Result<String> skuJson = skuInfoFeignClient.getValueSkuJson(skuInfoResult.getData().getSpuId());
                    result.setValuesSkuJson(skuJson.getData());
                }, executor);

        CompletableFuture
                .allOf(imageFuture,priceFuture,categoryFuture,saleAttrFuture,valueJsonFuture)
                .join();

        return result;
    }
}
